package org.example.httpproxy;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.proxy.HttpProxyHandler;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

/**
 * @author 刁海文
 * @date 2022/5/3 18:29
 */
public class HttpProxyClientHandler extends ChannelInboundHandlerAdapter {

    // 代理到远端服务器的 channel
    private Channel remoteChannel;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
        if (msg instanceof FullHttpRequest request) {
            // 客户端到代理的 channel
            final Channel clientChannel = ctx.channel();
            System.out.println(request);
            boolean isConnectMethod = HttpMethod.CONNECT.equals(request.method());

            // 解析目标主机host和端口号
            final SocketAddress remoteAddress = parseHostAndPort(request, isConnectMethod);

            System.out.println("remote server is " + remoteAddress);

            // disable AutoRead until remote connection is ready
            clientChannel.config().setAutoRead(false);

            // 建立代理服务器到目标主机的连接
            ChannelFuture connect = new Bootstrap().group(clientChannel.eventLoop()) // 和 clientChannel 使用同一个 EventLoop
                    .channel(clientChannel.getClass())
                    .handler(new HttpRequestEncoder())
                    .connect(remoteAddress);
            String host = "IP 地址";
            int port = 0; // 端口号
            String username = "用户名";
            String password = "密码";
            remoteChannel = connect.channel();
            // 添加用户名密码
            HttpProxyHandler httpProxyHandler = new HttpProxyHandler(new InetSocketAddress(host, port), username, password);
            remoteChannel.pipeline().addFirst(httpProxyHandler);
            connect.addListener(new ChannelListener(clientChannel, remoteChannel, isConnectMethod, request, this));
//            connect.addListener(new ChannelListener(clientChannel, remoteChannel, isConnectMethod, request));
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        flushAndClose(remoteChannel);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        flushAndClose(ctx.channel());
    }

    private void flushAndClose(Channel ch) {
        if (ch != null && ch.isActive()) {
            ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 解析header信息，建立连接
     * HTTP 请求头如下
     * GET http://www.baidu.com/ HTTP/1.1
     * Host: www.baidu.com
     * User-Agent: curl/7.69.1
     * Proxy-Connection:Keep-Alive
     * ---------------------------
     * HTTPS请求头如下
     * CONNECT www.baidu.com:443 HTTP/1.1
     * Host: www.baidu.com:443
     * User-Agent: curl/7.69.1
     * Proxy-Connection: Keep-Alive
     */
    private SocketAddress parseHostAndPort(FullHttpRequest httpRequest, boolean isConnectMethod) throws UnsupportedEncodingException {
        String hostAndPortStr;
        if (isConnectMethod) {
            // CONNECT 请求以请求行为准
            hostAndPortStr = httpRequest.uri();
        } else {
            hostAndPortStr = httpRequest.headers().get("Host");
        }
        int port;
        String[] hostPortArray = hostAndPortStr.split(":");
        if (hostPortArray.length == 2) {
            port = Integer.parseInt(hostPortArray[1]);
        } else if (isConnectMethod) {
            // 没有端口号，CONNECT 请求默认443端口
            port = 443;
        } else {
            // 没有端口号，普通HTTP请求默认80端口
            port = 80;
        }
        return new InetSocketAddress(hostPortArray[0], port);
    }
}
